﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Hiro.Containers;
using Hiro.Interfaces;
using LinFu.Reflection.Emit;
using Mono.Cecil;

namespace Hiro
{
    /// <summary>
    /// Represents a type that can dynamically compile <see cref="IDependencyMap"/> instances
    /// into a running <see cref="IMicroContainer"/> instance.
    /// </summary>
    public class ContainerCompiler : IContainerCompiler
    {
        /// <summary>
        /// The <see cref="IContainerBuilder"/> instance responsible for defining the
        /// implementation for the <see cref="IMicroContainer"/> class.
        /// </summary>
        private readonly IContainerBuilder _methodBuilder;

        /// <summary>
        /// Initializes a new instance of the <see cref="ContainerCompiler"/> class.
        /// </summary>
        public ContainerCompiler()
        {
            _methodBuilder = new ContainerBuilder();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ContainerCompiler"/> class.
        /// </summary>
        /// <param name="methodBuilder">The instance responsible for defining the implementation for the <see cref="IMicroContainer"/> class.</param>
        public ContainerCompiler(IContainerBuilder methodBuilder)
        {
            _methodBuilder = methodBuilder;
        }

        /// <summary>
        /// Compiles the given <see cref="IDependencyMap"/> instance into a single container type
        /// that implements the <see cref="IMicroContainer"/> interface.
        /// </summary>
        /// <param name="dependencyMap">The dependency map that describes the dependencies that need to be instantiated by the compiled container.</param>
        /// <returns>An <see cref="AssemblyDefinition"/> instance that contains the compiled container.</returns>
        public AssemblyDefinition Compile(IDependencyMap dependencyMap)
        {
            var assemblyName = Guid.NewGuid().ToString();
            var assembly = AssemblyFactory.DefineAssembly(assemblyName, AssemblyKind.Dll);
            var mainModule = assembly.MainModule;

            var objectType = mainModule.Import(typeof(object));
            var attributes = TypeAttributes.AutoClass | TypeAttributes.Class |
                             TypeAttributes.Public | TypeAttributes.BeforeFieldInit;

            var microContainerTypeRef = mainModule.Import(typeof(IMicroContainer));

            var guid = Guid.NewGuid().ToString().Replace("-", string.Empty);
            var typeName = string.Format(CultureInfo.InvariantCulture, "MicroContainer-{0}", guid);
            var namespaceName = "Hiro.Containers";
            var containerType = mainModule.DefineClass(typeName, namespaceName, attributes, objectType);

            // Implement the default constructor
            containerType.AddDefaultConstructor();
            containerType.Interfaces.Add(microContainerTypeRef);

            // Implement the IMicroContainer interface
            _methodBuilder.EmitGetInstance(containerType, mainModule, dependencyMap);
            _methodBuilder.EmitGetAllInstances(containerType, mainModule, dependencyMap);

            return assembly;
        }

        /// <summary>
        /// Compiles the given <see cref="IDependencyMap"/> instance into a single container type
        /// that implements the <see cref="IMicroContainer"/> interface.
        /// </summary>
        /// <param name="dependencyMap">The dependency map that describes the dependencies that need to be instantiated by the compiled container.</param>
        /// <returns>A <see cref="IMicroContainer"/> instance that represents the compiled container.</returns>
        IMicroContainer IContainerCompiler.Compile(IDependencyMap dependencyMap)
        {
            AssemblyDefinition assemblyDefinition = Compile(dependencyMap);
            var loadedAssembly = assemblyDefinition.ToAssembly();
            var containerType = (from t in loadedAssembly.GetTypes()
                                 where typeof(IMicroContainer).IsAssignableFrom(t)
                                 select t).First();

            var container = (IMicroContainer)Activator.CreateInstance(containerType);

            return container;
        }
    }
}
